
package com.github.ybq.parallaxviewpager;

import com.github.ybq.parallaxviewpager.Interpolator.DecelerateInterpolator;
import com.github.ybq.parallaxviewpager.Interpolator.Interpolator;
import ohos.agp.colors.RgbColor;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;
import ohos.agp.components.PageSlider;
import ohos.agp.components.PageSliderProvider;
import ohos.agp.components.element.ShapeElement;
import ohos.agp.render.Canvas;
import ohos.app.Context;

/**
 * The type Parallax view pager.
 */
public class ParallaxViewPager extends PageSlider implements Component.DrawTask {

    private static final String TAG = "ParallaxViewPager";

    private static final String PARALLAX_VIEW_PAGER_OUTSET = "outset";

    private static final String PARALLAX_VIEW_PAGER_MODE = "overlay_mode";

    private static final String PARALLAX_VIEW_PAGER_SHADOW_WIDTH = "shadow_width";

    private static int FIRST_PAGE = 0;

    private static int LAST_PAGE = 0;

    private ShapeElement mRightShadow;
    private ShapeElement mLeftShadow;
    private ParallaxTransformer mParallaxTransformer;
    private PageTransformer mPageTransformer;
    private PageChangedListener pageChangedListener;
    private Interpolator mInterpolator;
    private int mOutset;
    private int childCount = 0;
    private int mShadowWidth = 100;
    private int mOffsetPixels = 0;
    private int currentPagePosition = 0;
    private float mOutsetFraction = 0.5f;
    private float scrollX = 0;
    private Mode mMode;
    private boolean isPageSlideStateChanged = false;
    private PageChangedListener mPageChangedListener = new PageChangedListener() {

        @Override
        public void onPageSliding(int position, float offset, int offsetPixels) {
            if (pageChangedListener != null) {
                pageChangedListener.onPageSliding(position, offset, offsetPixels);
            }

            if (isPageSlideStateChanged && offset != 1) {
                isPageSlideStateChanged = false;
                invalidate();
                return;
            }

            ComponentContainer componentContainer = (ComponentContainer) getComponentAt(0);
            if (judgeViewPosition(position, offsetPixels, componentContainer)){
                return;
            }

            judgeState(position, offset, offsetPixels, componentContainer);
        }

        @Override
        public void onPageSlideStateChanged(int position) {
            LogUtil.info(TAG, " onPageSlideStateChanged: " + position + " getCurrentPage(): " + getCurrentPage());
            ParallaxViewPager.this.onPageSlideStateChanged(position);
        }

        @Override
        public void onPageChosen(int position) {
            LogUtil.info(TAG, " onPageChosen: " + position);
            onPageCHose(position);
        }
    };

    private boolean judgeViewPosition(int position, int offsetPixels, ComponentContainer componentContainer) {
        if (mPageTransformer != null) {
            scrollX = offsetPixels + position * getClientWidth();
            childCount = componentContainer.getChildCount();
            for (int i = 0; i < childCount; i++) {
                final ComponentContainer childContainer = (ComponentContainer) componentContainer.getComponentAt(i);
                int left = childContainer.getLeft();
                if (LAST_PAGE > 1) {
                    if (position >= 2 && position < LAST_PAGE) {
                        left = left + (position - 1) * getClientWidth();
                    } else if (position == LAST_PAGE) {
                        left = left + (position - 2) * getClientWidth();
                    }
                }

                Component child = childContainer.getComponentAt(0);
                float transformPos = (float) (left - scrollX) / getClientWidth();

                if (currentPagePosition > position && currentPagePosition != FIRST_PAGE + 1 && currentPagePosition != LAST_PAGE) {
                    transformPos = transformPos + 1;
                } else if (currentPagePosition < position && currentPagePosition != FIRST_PAGE && currentPagePosition != LAST_PAGE - 1) {
                    transformPos = transformPos - 1;
                }

                if (child == null){
                    return true;
                }

                mPageTransformer.transformPage(child, transformPos, position, currentPagePosition);
                mOffsetPixels = offsetPixels;
                invalidate();
            }
        }
        return false;
    }

    private void onPageSlideStateChanged(int position) {
        scrollX = getCurrentPage() * getClientWidth();
        if (pageChangedListener != null) {
            pageChangedListener.onPageSlideStateChanged(position);
        }
    }

    private void onPageCHose(int position) {
        currentPagePosition = position;
        isPageSlideStateChanged = true;
        if (pageChangedListener != null) {
            pageChangedListener.onPageChosen(position);
        }
    }

    private void judgeState(int position, float offset, int offsetPixels, ComponentContainer componentContainer) {
        if (Math.abs(offset - 1) < 1e-6f) {
            for (int i = 0; i < childCount; i++) {
                final ComponentContainer childContainer = (ComponentContainer) componentContainer.getComponentAt(i);
                Component child = childContainer.getComponentAt(0);
                mPageTransformer.transformPage(child, 0, position, i);
                mOffsetPixels = offsetPixels;
                invalidate();
            }
        }
    }

    /**
     * Instantiates a new Parallax view pager.
     *
     * @param context the context
     */
    public ParallaxViewPager(Context context) {
        this(context, null);
    }

    /**
     * Instantiates a new Parallax view pager.
     *
     * @param context the context
     * @param attrSet the attr set
     */
    public ParallaxViewPager(Context context, AttrSet attrSet) {
        this(context, attrSet, null);

    }

    /**
     * Instantiates a new Parallax view pager.
     *
     * @param context   the context
     * @param attrSet   the attr set
     * @param styleName the style name
     */
    public ParallaxViewPager(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
        mParallaxTransformer = new ParallaxTransformer();
        if (attrSet.getAttr(PARALLAX_VIEW_PAGER_OUTSET).isPresent()) {
            mOutsetFraction = attrSet.getAttr(PARALLAX_VIEW_PAGER_OUTSET).get().getFloatValue();
            setOutsetFraction(mOutsetFraction);
        }
        if (attrSet.getAttr(PARALLAX_VIEW_PAGER_SHADOW_WIDTH).isPresent()) {
            mShadowWidth = attrSet.getAttr(PARALLAX_VIEW_PAGER_SHADOW_WIDTH).get().getIntegerValue();
        }
        if (attrSet.getAttr(PARALLAX_VIEW_PAGER_MODE).isPresent()) {
            int mode = attrSet.getAttr(PARALLAX_VIEW_PAGER_MODE).get().getIntegerValue();
            LogUtil.info(TAG, "mode: " + mode);
            if (mode >= 0 && mode < Mode.values().length) {
                mMode = Mode.values()[mode];
            } else {
                mMode = Mode.values()[0];
            }
            setSlideMode(mMode);
        }
        initShadow();
        ensureInterpolator();
        super.addPageChangedListener(mPageChangedListener);
        addDrawTask(this);
        isPageSlideStateChanged = false;
    }

    private void initShadow() {
        RgbColor mShadowStart = new RgbColor(0x00000033);
        RgbColor mShadowMid = new RgbColor(0x00000011);
        RgbColor mShadowEnd = new RgbColor(0x00000000);
        RgbColor[] rgbColors = new RgbColor[]{mShadowStart, mShadowMid, mShadowEnd};
        ShapeElement rightShadow = new ShapeElement();
        rightShadow.setOrientation(ShapeElement.Orientation.LEFT_TO_RIGHT);
        rightShadow.setShaderType(ShapeElement.LINEAR_GRADIENT_SHADER_TYPE);
        rightShadow.setRgbColors(rgbColors);
        mRightShadow = rightShadow;
        ShapeElement leftShadow = new ShapeElement();
        leftShadow.setOrientation(ShapeElement.Orientation.RIGHT_TO_LEFT);
        leftShadow.setShaderType(ShapeElement.LINEAR_GRADIENT_SHADER_TYPE);
        leftShadow.setRgbColors(rgbColors);
        mLeftShadow = leftShadow;
    }

    @Override
    public void setProvider(PageSliderProvider provider) {
        super.setProvider(provider);
        if (getProvider() != null) {
            LAST_PAGE = getProvider().getCount() - 1;
        }
    }

    /**
     * Draw shadow.
     *
     * @param canvas the canvas
     */
    public void drawShadow(Canvas canvas) {
        if (mMode == Mode.NONE) {
            return;
        }
        if (scrollX % getWidth() == 0) {
            return;
        }
        switch (mMode) {
            case LEFT_OVERLAY:
                drawRightShadow(canvas);
                break;
            case RIGHT_OVERLAY:
                drawLeftShadow(canvas);
                break;
        }
    }

    private void drawRightShadow(Canvas canvas) {
        canvas.save();
        if (mOffsetPixels > 0) {
            mRightShadow.setBounds(getWidth() - mOffsetPixels, 0, getWidth() - mOffsetPixels + mShadowWidth, getHeight());
        } else {
            mRightShadow.setBounds(-mOffsetPixels, 0, -mOffsetPixels + mShadowWidth, getHeight());
        }
        mRightShadow.drawToCanvas(canvas);
        canvas.restore();
    }

    private void drawLeftShadow(Canvas canvas) {
        canvas.save();
        if (mOffsetPixels > 0) {
            mLeftShadow.setBounds(getWidth() - mShadowWidth - mOffsetPixels, 0, getWidth() - mOffsetPixels, getHeight());
        } else {
            mLeftShadow.setBounds(-mOffsetPixels - mShadowWidth, 0, -mOffsetPixels, getHeight());
        }
        mLeftShadow.drawToCanvas(canvas);
        canvas.restore();
    }


    @Override
    public void addPageChangedListener(PageChangedListener listener) {
        pageChangedListener = listener;
    }

    /**
     * Sets page transformer.
     *
     * @param transformer the transformer
     */
    public void setPageTransformer(PageTransformer transformer) {
        mPageTransformer = transformer;
    }

    private int getClientWidth() {
        return getWidth() - getPaddingLeft() - getPaddingRight();
    }


    /**
     * Sets outset fraction.
     *
     * @param outsetFraction the outset fraction
     */
    public void setOutsetFraction(float outsetFraction) {
        this.mOutsetFraction = outsetFraction;
        mOutset = 0;
        mParallaxTransformer.setOutsetFraction(mOutsetFraction);
    }


    /**
     * Sets interpolator.
     *
     * @param mInterpolator the m interpolator
     */
    public void setInterpolator(Interpolator mInterpolator) {
        this.mInterpolator = mInterpolator;
        ensureInterpolator();
    }

    /**
     * Ensure interpolator.
     */
    protected void ensureInterpolator() {
        if (mInterpolator == null) {
            mInterpolator = new DecelerateInterpolator();
        }
        if (mParallaxTransformer != null) {
            mParallaxTransformer.setInterpolator(mInterpolator);
        }
    }

    /**
     * Sets slide mode.
     *
     * @param mode the mode
     */
    public void setSlideMode(Mode mode) {
        mMode = mode;
        mParallaxTransformer.setMode(mode);
        setPageTransformer(mParallaxTransformer);
    }

    /**
     * Gets slide mode.
     *
     * @return the slide mode
     */
    public Mode getSlideMode() {
        return mMode;
    }

    /**
     * Gets outset.
     *
     * @return the outset
     */
    public int getOutset() {
        return mOutset;
    }

    /**
     * Sets outset.
     *
     * @param outset the outset
     */
    public void setOutset(int outset) {
        this.mOutset = outset;
        mOutsetFraction = 0;
        mParallaxTransformer.setOutset(mOutset);
    }

    /**
     * Gets outset fraction.
     *
     * @return the outset fraction
     */
    public float getOutsetFraction() {
        return mOutsetFraction;
    }

    /**
     * Sets right shadow.
     *
     * @param rightShadow the right shadow
     */
    public void setRightShadow(ShapeElement rightShadow) {
        this.mRightShadow = rightShadow;
    }

    /**
     * Sets left shadow.
     *
     * @param leftShadow the left shadow
     */
    public void setLeftShadow(ShapeElement leftShadow) {
        this.mLeftShadow = leftShadow;
    }

    /**
     * Gets interpolator.
     *
     * @return the interpolator
     */
    public Interpolator getInterpolator() {
        return mInterpolator;
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        drawShadow(canvas);
    }

    /**
     * The interface Page transformer.
     */
    public interface PageTransformer {
        /**
         * Transform page.
         *
         * @param page            the page
         * @param position        the position
         * @param currentPosition the current position
         * @param viewPosition    the view position
         */
        void transformPage(Component page, float position, int currentPosition, int viewPosition);
    }

}
